/*-
 * Copyright (c) 2007-2008 SINA Corporation, All Rights Reserved.
 *  Authors:
 *      Zhu Yan <zhuyan@staff.sina.com.cn>
 *
 * Sample library file store Implementation
 */

#include <stdio.h>
#include <stdint.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>
#include <sys/uio.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <parsemail/parsemail.h>

#include "crc32.h"
#include "lmtp.h"
#include "log.h"
#include "libmod.h"
#include "confparser.h"
#include "mod_conf.h"

int mod_init(char *mod_name);
static int mod_conf_stage_id;
#define FILEOP_ERROR        mod_conf_stage_id
#define BASE_DIR        "/sl"

static char conf_fileop_base_dir[CONF_ITEM_LEN + 1] = BASE_DIR;

static CONF_STR_CONFIG conf_str_array[] = {
    {"base_dir", conf_fileop_base_dir}, 
    {0, 0}
};

static CONF_INT_CONFIG conf_int_array[] = {
    {"stage_id", &mod_conf_stage_id}, 
    {0, 0}
};

int
testparse(LMTP_DATA * ldp, const char *dest_dir);
/*
 * parse mail example
 */
int
testparse(LMTP_DATA * ldp, const char *dest_dir)
{
    FILE *f;
    int i;
    int count;
    header_info re;
    text_info rep;
    mbfl_string result, *retv; /* convert charset */
    attachment_info ret;
    int hcount;
    char buf[1024];
    MAIL_HANDLE* pm;
    char path_dir[PATH_MAX * 3 + 1]; 
    int ren;

    pm = ldp->pm;
    /* record mailpath for trace parse error */
    set_mailpath(pm, dest_dir);
    /******************* parse mail ***************/
    while (1) {
        memset(buf, 0, 1024);
        ren = mread_line(ldp->data_buffer, (char *)buf, 1023);
        if (0 == ren) {
            break;
        }
        if (parseline(pm, buf, (int)strlen(buf)) == ERROR) {
               fprintf(stderr, "parse ERROR:%s\n", ldp->pm->mailpath);
               return (PIPELINE_FAILED);
        }
    }
    /*******************************************/
    
    memset(path_dir, 0, PATH_MAX * 3 + 1);
    snprintf(path_dir, PATH_MAX * 3, "%s%s", dest_dir, "_header");
    /* get header info */
    if ((f = fopen(path_dir, "wb")) == NULL) {
        fprintf(stderr, "create header file error!\n");
        return (ERROR);
    }
    /*Received*/
    if ((hcount = mget_header_count(pm, Received)) > 0) {
        for (i = 1; i <= hcount; ++i) {
            if (get_header_value(pm, &re, Received, i) == 1) {
                fprintf(f, "received: %s\n", re.value);
            }
        }
    }
    /*Subject*/
    if (get_header_value(pm, &re, Subject, 1) == 1) {
        fprintf(f, "subject: %s\n", re.value); 
      
    }
    if (get_header_value_decode(pm, &re, Subject, 1) == 1) {
        fprintf(f, "subject_decode(charset:%s): %s\n", re.charset, re.value);
        /* convert charset */
        retv = to_utf8(&result, re.value, re.charset);
        mbfl_string_clear(retv);
    }
    /*From*/
    if (get_header_value(pm, &re, From, 1) == 1) {
        fprintf(f, "from: %s\n", re.value); 
    }
    if (get_header_value_decode(pm, &re, From, 1) == 1) {
        fprintf(f, "from_decode(charset:%s): %s\n", re.charset, re.value);
    }
    /*To*/
    if (get_header_value(pm, &re, To, 1) == 1) {
        fprintf(f, "to: %s\n", re.value);
    }
    fflush(f);
    if (get_header_value_decode(pm, &re, To, 1) == 1) {
        fprintf(f, "to_decode(charset:%s):%s\n", re.charset, re.value);
    }
    fflush(f);
    /*Message_ID*/
    if (get_header_value(pm, &re, Message_ID, 1) == 1) {
        fprintf(f, "Message-ID: %s", re.value);
    }
    fclose(f);

    memset(path_dir, 0, PATH_MAX * 3 + 1);
    snprintf(path_dir, PATH_MAX * 3, "%s%s", dest_dir, "_text");
    int fd;	
    if ((fd = open(path_dir, O_RDWR|O_CREAT|O_EXCL, S_IRWXU|S_IRWXG|S_IROTH)) == -1) {
        fprintf(stderr, "create header file error!\n");
        return (ERROR);
    }
    if (get_text_text_decode(pm, &rep) == 1) {
        mwrite_file(rep.mfile, fd);
    }
    close(fd);

    memset(path_dir, 0, PATH_MAX * 3 + 1);
    snprintf(path_dir, PATH_MAX * 3, "%s%s", dest_dir, "_html");
    if ((f = fopen(path_dir, "wb")) == NULL) {
        fprintf(stderr, "create header file error!\n");
        return (ERROR);
    }
    if (get_html_text_decode(pm, &rep) == 1) {
        mwrite_file(rep.mfile, fileno(f));
    }
    fclose(f);

    /* get attachement */
    count = get_attachment_count(pm);
    for (i = 1; i <= count; ++i) {
        if (get_attachment_decode(pm, &ret, i) == 0) {
            fprintf(stderr, "get get_attachment_decode error\n");
            continue;
        } 
        memset(path_dir, 0, PATH_MAX * 3 + 1);
        snprintf(path_dir, PATH_MAX * 3, "%s_%s", dest_dir, ret.name);
        f = fopen(path_dir, "wb");
        if (NULL == f) {
            fprintf(stderr, "can't creat file:%s\n", path_dir);
            continue;
        }
        /* write to file */
        mwrite_file(ret.mfile, fileno(f));
        fclose(f);
    }
    /* get inline attachment */
    count = get_inline_count(pm);
    for (i = 1; i <= count; ++i) {
        if (get_inline_decode(pm, &ret, i) == 0) {
            fprintf(stderr, "get get_inline_decode error\n");
            continue;
        }  
        memset(path_dir, 0, PATH_MAX * 3 + 1);
        snprintf(path_dir, PATH_MAX * 3, "%s_%d.%s", dest_dir, i, ret.minor_type);
        f = fopen(path_dir, "wb");
        if (NULL == f) {
            fprintf(stderr, "can't creat file:%s\n", path_dir);
            continue;
        }
        mwrite_file(ret.mfile, fileno(f));
        fclose(f);
    }
    return (OK);
}

/*
 * Returns 1 if a directory has been created,
 * 2 if it already existed, and 0 on failure.
 */
static int 
build_dir(char* path, mode_t omode)
{
    struct stat sb;
    mode_t numask, oumask;
    int first, last, retval;
    char* p;

    p = path;
    oumask = 0;
    retval = 1;
    if (p[0] == '/')        /* Skip leading '/'. */
        ++p;
    for (first = 1, last = 0; !last ; ++p) {
        if (p[0] == '\0')
            last = 1;
        else if (p[0] != '/')
            continue;
        *p = '\0';
        if (!last && p[1] == '\0')
            last = 1;
        if (first) {
            /*
             * POSIX 1003.2:
             * For each dir operand that does not name an existing
             * directory, effects equivalent to those caused by the
             * following command shall occcur:
             *
             * mkdir -p -m $(umask -S),u+wx $(dirname dir) &&
             *    mkdir [-m mode] dir
             *
             * We change the user's umask and then restore it,
             * instead of doing chmod's.
             */
            oumask = umask(0);
            numask = oumask & ~(S_IWUSR | S_IXUSR);
            (void) umask(numask);
            first = 0;
        }
        if (last)
            (void) umask(oumask);
        if (mkdir(path, last ? omode : S_IRWXU | S_IRWXG | S_IRWXO) < 0) {
            if (errno == EEXIST || errno == EISDIR) {
                if (stat(path, &sb) < 0) {
                    retval = 0;
                    break;
                } else if (!S_ISDIR(sb.st_mode)) {
                    if (last)
                        errno = EEXIST;
                    else
                        errno = ENOTDIR;
                    retval = 0;
                    break;
                }
                if (last)
                    retval = 2;
            } else {
                retval = 0;
                break;
            }
        }
        if (!last)
            *p = '/';
    }
    if (!first && !last)
        (void) umask(oumask);
    return (retval);
}

static int
write_file(const char *filename, MFILE *data)
{
    int fd;
    int retval;

    fd = open(filename, O_RDWR|O_CREAT|O_EXCL, S_IRWXU|S_IRWXG|S_IROTH);
    
    if (fd < 0) {
        WLOG_ERR("fd: %d, i: %d, err: %s\n", fd, strerror(errno));
        return (-1);
    }
    retval = mwrite_file(data, fd);
    if (retval < 0) {
        WLOG_ERR("fd: %d, i: %d, err: %s\n", fd, strerror(errno));
        close(fd);
        return (-1);
    }
    
    WLOG_DEBUG("Create file OK!");
    close(fd);
    return (0);
}

static ssize_t 
file_handler(WTI *wti, CONN_INFO *cip, void *data, void **arg)
{
    static uint32_t c = 0;
    int retval;
    char oldpath[PATH_MAX];
    char filepath[PATH_MAX];
    char dirname[PATH_MAX];
    RCPT *rp = NULL;
    LMTP_DATA *ldp = NULL;
    uint32_t rcpt_crc;
    uint8_t* c_crc;
    struct tm tr;

    CSF_UNUSED_ARG(arg);
    CSF_UNUSED_ARG(cip);
    CSF_UNUSED_ARG(wti);

    oldpath[0] = 0;

    ldp = (LMTP_DATA *)data;

    localtime_r(&(ldp->mi.time), &tr);

    STAILQ_FOREACH(rp, &(ldp->mi.rcpt_head), rcpt_entries) {
        rcpt_crc = crc32((uint8_t*)rp->rcpt, rp->rcpt_len);
        c_crc = (uint8_t*)(&rcpt_crc);

        snprintf(dirname, PATH_MAX - 1,
            "%s/%02x/%02x/%02x/%02x/%s_%02d%02d%02d%02d",
            conf_fileop_base_dir,
            c_crc[0], c_crc[1], c_crc[2], c_crc[3],
            rp->rcpt, tr.tm_year + 1900, tr.tm_mon + 1, tr.tm_mday, tr.tm_hour);

        retval = build_dir(dirname, S_IRWXU | S_IRWXG);
        if (retval == 0) {
            rp->rc = FILEOP_ERROR;
            continue;
        }

        /* Build filename */
        /* XXX ++c is not thread safe, it could make filename repeated */
        snprintf(filepath, PATH_MAX - 1, 
            "%s/%d%p%d", dirname, (uint32_t)ldp->mi.time, dirname, ++c);

        if (!oldpath[0]) {
            strlcpy(oldpath, filepath, PATH_MAX);
            retval = write_file(filepath, ldp->data_buffer);
	     testparse(ldp, filepath);
            snprintf(ldp->command_file, PATH_MAX, "%s%s", filepath, "_text");
            snprintf(ldp->file_path, PATH_MAX, "%s", filepath);
            if (retval != 0) {
                rp->rc = FILEOP_ERROR;
                continue;
            }
        } else {
            retval = link(oldpath, filepath);
            if (retval != 0) {
                retval = write_file(filepath, ldp->data_buffer);
                testparse(ldp, filepath);
		  snprintf(ldp->command_file, PATH_MAX, "%s%s", filepath, "_text");
                snprintf(ldp->file_path, PATH_MAX, "%s", filepath);
                if (retval != 0) {
                    rp->rc = FILEOP_ERROR;
                    continue;
                }
            } else {
                WLOG_DEBUG("Create hardlink OK!");
            }
        }
        strlcpy(rp->path, filepath, PATH_MAX);
    }
    return (PIPELINE_CONTINUE);
}

int 
mod_init(char *mod_name)
{
    int r;

    r = load_conf(NULL, mod_name, conf_int_array, conf_str_array);
    if (r != 0)
        WLOG_ERR("load_conf() failed!");

    set_request_init(NULL);
    set_request_deinit(NULL);
    set_request_handler(file_handler);

    return (0);
}


